//
// Created by entaoyang@163.com on 2021/8/2.
//

import Foundation

public extension Dictionary {
    var toMap: Map<Key, Value> {
        Map(dic: self)
    }
}

public final class Map<Key: Hashable, Value: Any>: NSObject, Sequence, ExpressibleByDictionaryLiteral, CustomReflectable {
    public typealias Element = (key: Key, value: Value)

    fileprivate var dic: [Key: Value] = [:]

    public convenience init(capcity: Int) {
        self.init()
        dic.reserveCapacity(capcity)
    }

    public convenience init(dic: [Key: Value]) {
        self.init(capcity: dic.capacity)
        self.dic.putAll(dic: dic)
    }

    public override init() {
        super.init()
        dic.reserveCapacity(16)
    }

    required public init(dictionaryLiteral elements: (Key, Value)...) {
        dic.reserveCapacity(elements.count)
        for e in elements {
            dic[e.0] = e.1
        }
    }

    public init<S>(uniqueKeysWithValues keysAndValues: S) where S: Sequence, S.Element == (Key, Value) {
        let n = keysAndValues.underestimatedCount
        if n > 0 {
            dic.reserveCapacity(n)
        }
        for e in keysAndValues {
            dic[e.0] = e.1
        }
    }

    public required init(from decoder: Decoder) throws where Key: Decodable, Value: Decodable {
        dic = try Dictionary<Key, Value>(from: decoder)
    }

    var toDic: Dictionary<Key, Value> {
        self.dic
    }

    public func encode(to encoder: Encoder) throws where Key: Encodable, Value: Encodable {
        try dic.encode(to: encoder)
    }


    public func makeIterator() -> Dictionary<Key, Value>.Iterator {
        dic.makeIterator()
    }


    public override var description: String {
        dic.description
    }

    public override var debugDescription: String {
        dic.debugDescription
    }
    public var customMirror: Mirror {
        dic.customMirror
    }

}

public extension Map where Value: Equatable {
    static func ==(lhs: Map<Key, Value>, rhs: Map<Key, Value>) -> Bool where Value: Equatable {
        lhs.dic == rhs.dic
    }
}

public extension Map where Value: Hashable {
    func hash(into hasher: inout Hasher) {
        dic.hash(into: &hasher)
    }

    var hashValue: Int {
        dic.hashValue
    }
}


public extension Map {
    subscript(key: Key, default defaultValue: @autoclosure () -> Value) -> Value {
        dic[key, default: defaultValue()]
    }

    func mapValues<T>(_ transform: (Value) throws -> T) rethrows -> [Key: T] {
        try dic.mapValues(transform)
    }

    func compactMapValues<T>(_ transform: (Value) throws -> T?) rethrows -> [Key: T] {
        try dic.compactMapValues(transform)
    }

    func updateValue(_ value: Value, forKey key: Key) -> Value? {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return dic.updateValue(value, forKey: key)
    }

    func merge<S>(_ other: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S: Sequence, S.Element == (Key, Value) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return try dic.merge(other, uniquingKeysWith: combine)
    }

    func merge(_ other: [Key: Value], uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return try dic.merge(other, uniquingKeysWith: combine)
    }

    func merging<S>(_ other: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [Key: Value] where S: Sequence, S.Element == (Key, Value) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return try dic.merging(other, uniquingKeysWith: combine)
    }

    func merging(_ other: [Key: Value], uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [Key: Value] {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return try dic.merging(other, uniquingKeysWith: combine)
    }

    func remove(at index: Dictionary<Key, Value>.Index) -> Dictionary<Key, Value>.Element {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return dic.remove(at: index)
    }

    @discardableResult
    func removeValue(forKey key: Key) -> Value? {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return dic.removeValue(forKey: key)
    }

    func removeAll(keepingCapacity keepCapacity: Bool = false) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return dic.removeAll(keepingCapacity: keepCapacity)
    }

}

public extension Map {
    var dicValue: Dictionary<Key, Value> {
        dic
    }
    var keys: Dictionary<Key, Value>.Keys {
        dic.keys
    }

    var values: Dictionary<Key, Value>.Values {
        dic.values
    }
    var isEmpty: Bool {
        dic.isEmpty
    }

    var count: Int {
        dic.count
    }
    var capacity: Int {
        dic.capacity
    }

    func reserveCapacity(_ minimumCapacity: Int) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        dic.reserveCapacity(minimumCapacity)
    }

    func put(key: Key, value: Value) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        dic[key] = value
    }

    func get(key: Key) -> Value? {
        dic[key]
    }

    func get(key: Key, default defaultValue: @autoclosure () -> Value) -> Value {
        dic[key, default: defaultValue()]
    }

    subscript(_ key: Key) -> Value? {
        get {
            self.enterSync()
            defer {
                self.exitSync()
            }
            return dic[key]
        }
        set {
            self.enterSync()
            defer {
                self.exitSync()
            }
            dic[key] = newValue
        }
    }

    @discardableResult
    func remove(key: Key) -> Value? {
        self.removeValue(forKey: key)
    }

    func contains(key: Key) -> Bool {
        self.enterSync()
        defer {
            self.exitSync()
        }
        return self.keys.contains(key)
    }

}

